require 'camping/db'

gem 'acts_as_taggable'
require 'taggable'

module Yarbo::Models

  class Tag < Base  
  end
  
  class TagPost < Base
  end
  
  class Post < Base
    acts_as_taggable :tag_class_name => "Yarbo::Models::Tag",
      :join_class_name => 'TagPost'
    
    def body
      Body.new(super)
    end
    
    def dom_id(suffix = nil)
      dom_id = ""
      
      if new_record?
        dom_id = "new_#{self.class}_#{object_id}"
      else
        dom_id = "#{selc_class}_#{id}"
      end
      
      dom_id += "_#{suffix}" if suffix
      
      dom_id
    end
    
    def validate
      begin
        body.to_xhtml
      rescue InvalidMarkup
        errors.add(:body, "is invalid. #{$!}")
      end
    end
    
    class Body
      OPEN_CODE = /(\[code:?(.*?)\])/
      CLOSE_CODE = '[/code]'
      
      attr_accessor :text
      
      def initialize(text)
        @text = text
      end
      
      def to_xhtml
        self.class.split_into_blocks(text).collect{|b| b.to_xhtml}.join
      end
      
      class Block < OpenStruct
        def to_xhtml
          case kind
          when :textile then return RedCloth.new(text).to_html
          when :code then return CodeRay.scan(text, language).html(:css => :class).div
          else raise "#{type} is not a valid #{self.class} type." end
        end
      end
      
      private
        # Recursively splits and returns the text as an array of Blocks.
        # Currently there are only two types of blocks :code, and :textile.
        #
        # For example, the following text:
        #
        #   This is some *textile* markup.
        #   and some more... And below is ruby code:
        #
        #   [code:ruby]
        #     def example
        #       return true
        #     end
        #   [/code]
        #   
        #   And _some more_ textile.
        #
        # ... would be returned as the following array:
        #
        #   [#<Post::Body::Block kind=:textile, text="This is some *textile* markup.\nand some more... And below is ruby code:\n\n">,
        #     #<Post::Body::Block kind=:code, text="\ndef example\n  return true\nend\n", language=:ruby>,
        #     #<Post::Body::Block kind=:textile, text="\n   \nAnd _some more_ textile.\n">]
        # 
        # Note the 'kind' and 'language' attributes on each of those Post::Body::Block objects.
        def self.split_into_blocks(text)
          block = Block.new
          open_code = text.index(OPEN_CODE)
          
          if open_code.nil?
            block.kind = :textile
            block.text = text
          elsif open_code == 0
            close_code = text.index(CLOSE_CODE)
            raise InvalidMarkup, "You are missing a #{CLOSE_CODE} tag to close the code block." unless close_code
            block.kind = :code
            block.language = $2.to_sym
            block.text = text[$1.length..close_code-1]
            remainder = text[close_code+CLOSE_CODE.length..-1]
          else
            block.kind = :textile
            block.text = text[0..open_code-1]
            remainder = text[open_code..-1]
          end
          
          if remainder
            return [block] + split_into_blocks(remainder)
          else
            return [block]
          end
        end
    end
    
    class InvalidMarkup < Exception
    end
  end
  

  class CreateYarbo < V 0.1
    def self.up
      $LOG.info("Creating database")
      
      create_table :yarbo_authors, :force => true do |t|
        t.column :username, :string, :null => false
        t.column :display_name, :string
        t.column :email, :string
      end
      
      create_table :yarbo_posts, :force => true do |t|
        t.column :title, :string
        t.column :created_on, :datetime
        t.column :body, :text
        t.column :author_id, :integer, :null => false
        t.column :posted, :boolean, :null => false, :default => true
      end
      
      create_table :yarbo_tags do |t|
        t.column :name, :string, :null => false
      end
      
      create_table :yarbo_tag_posts, :force => true do |t|
        t.column :post_id, :integer, :null => false
        t.column :tag_id, :integer, :null => false
      end
    end
    
    def self.down
      drop_table :yarbo_tag_posts
      drop_table :yarbo_tags
      drop_table :yarbo_posts
      drop_table :yarbo_authors
    end
  end
end